#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include "task.hpp"

using namespace std;

enum
{
    UsageError = 1,
	ArgError,
	PipeError
};

//管道类
class Channel
{
public:
	Channel(int wfd,pid_t spid,const string &name)
	:_wfd(wfd),_sub_process_id(spid),_name(name)
	{
	}

	~Channel()
	{
	}

	int wfd() {return _wfd;}
	pid_t sub_process_id(){return _sub_process_id;}
	string name(){return _name;}
	void Close(){ close(_wfd);}
	void PrintDebug()
    {
        cout << "_wfd: " << _wfd;
        cout << ",_sub_process_id: " << _sub_process_id;
        cout << ", _name: " << _name << endl;
    }

private:
	int _wfd;
	pid_t _sub_process_id;
	string _name;
};

//进程池
class processpool
{
public:
	processpool(int sub_process_num)
	:_sub_process_num(sub_process_num)
	{}

	~processpool()
	{}

	int ProcesszCreate(work_t work)
	{
		vector<int> fds;
		for(int number = 0 ; number < _sub_process_num ; number++)
		{
			//创建管道
			int pipefd[2]{0};
			int n = pipe(pipefd);
			if(n < 0)
				return PipeError;
			
			pid_t id = fork();
			if(id == 0)
			{
				//这一步是为了关闭子进程拷贝父进程后文件描述符依然会指向管道写端的bug
				if(!fds.empty())
				{
					cout<<"close w fd:";
					for(auto &fd : fds)
					{
						close(fd);
						cout<<fd<<" ";
					}

					cout<<endl;
				}

				//child r 这里要让子进程读取命令知道自己要执行什么
				close(pipefd[1]);		//关闭写
				dup2(pipefd[0],0);
				work(pipefd[0]);
				exit(0);
			}

			string cname = "channel-" + to_string(number);

			close(pipefd[0]);		//关闭读
			_channels.push_back(Channel(pipefd[1],id,cname));		//组织起来

			fds.push_back(pipefd[1]);
		}
		return 0;
	}

	int NextChannel()
	{
		static int next = 0;
		int c = next;
		next++;
		next %= _channels.size();
		return c;
	}

	void SentTaskCode(int index,uint32_t code)
	{
		cout<<"sent code:"<<code<<"to"<<_channels[index].name()<<"sub process id:"<<_channels[index].sub_process_id()<<endl;
		write(_channels[index].wfd(),&code,sizeof(code));
	}

	void KillAll()
	{
		for(auto &channel : _channels)
		{
			channel.Close();
			pid_t pid = channel.sub_process_id();

			cout << channel.name() << " close done" << " sub process quit now : " << channel.sub_process_id() << std::endl;
		}
	}

	void WaitAll()
	{
		for(auto &channel : _channels)
		{
			pid_t pid = channel.sub_process_id();
			pid_t rpid = waitpid(pid,nullptr,0);
			if(rpid == pid)
			{
				std::cout << "wait sub process: " << pid << " success..." << std::endl;
			}
		}
	}

	void Kill_WaitAll()
	{
		for(auto &channel : _channels)
		{
			channel.Close();
			pid_t pid = channel.sub_process_id();
			pid_t rpid = waitpid(pid,nullptr,0);
			if(rpid == pid)
			{
				std::cout << "wait sub process: " << pid << " success..." << std::endl;
			}
			cout << channel.name() << " close done" << " sub process quit now : " << channel.sub_process_id() << std::endl;
		}
	}
	void Debug()
    {
        for (auto &channel : _channels)
        {
            channel.PrintDebug();
        }
    }
private:
	int _sub_process_num;
	vector<Channel> _channels;
};

void CtrlProcessPool(processpool* processpool_ptr , int cnt)
{

	while(cnt)
	{
		//选择一个信道和进程
		int channel = processpool_ptr->NextChannel();
		
		//选择一个任务
		uint32_t code = NextTask();

		//发送任务
		processpool_ptr->SentTaskCode(channel,code);

		sleep(1);

		cnt--;
	}
}

//提示使用方法
void Usage(const string &proc)
{
    cout<<"Usage:"<<proc<<"subprocess-num"<<endl;
}

int main(int argc,char* argv[])
{
    //如果用法错误会提示
    if(argc != 2)
    {
        Usage(argv[0]);
        return UsageError;
    }

	int sub_process_num = stoi(argv[1]);	//获取要创建进程的个数
	if(sub_process_num <= 0) return ArgError;

	srand((uint64_t)time(nullptr));		//随机数种子

	//1、创建通信信道和子进程	
	processpool* processpool_ptr = new processpool(sub_process_num);
	processpool_ptr->ProcesszCreate(worker);
	//processpool_ptr->Debug();

	//2、控制子进程
	CtrlProcessPool(processpool_ptr,5);
	
	cout<<"task run done"<<endl;
	//sleep(100);

	//3、回收进程	可以采用先全部退出再回收，也可以退出一个回收一个（但是这个前面有一个bug，因为子进程都是拷贝父进程，
	//所以有的子进程的文件描述符会指向之前的写端，导致子进程无法退出阻塞）
	//先要让所有进程退出，通过关闭写端来控制
	// processpool_ptr->KillAll();
	//再对所有进程进行回收
	// processpool_ptr->WaitAll();
	processpool_ptr->Kill_WaitAll();
	delete processpool_ptr;

    return 0;
}